
#include <iostream>
#include "FilterCompressor.h"
#define abs(a) (a)>0?(a):(-a)
#define sqr(a) ((a) * (a))
#define bezier_quad(p0, p1, p2, t)  (p0)*sqr(1-(t)) + 2*(t)*(1-(t))*(p1) + (p2) * sqr(t)

namespace libtrckr {

string FilterCompressor::HowToTune(){
	return "Band 0 +20; Band 0 +20; Check; Spin -20 0; Spin +1 +10; Check; Bitrate";
}

FilterCompressor::FilterCompressor():attack(0.2),delay(0.1),soft(0),type(NORMAL),ratio(2),floor(-10.),bitrate(44100){

}

FilterCompressor::FilterCompressor(const FilterCompressor &fc):attack(fc.attack), delay(fc.delay), soft(fc.soft),ratio(fc.ratio),type(fc.type),bitrate(fc.bitrate){

}

void FilterCompressor::Init(){
	if(soft){
		/* la pente de la courbe avant le soft knew est de 1 => y(x)=x;*/
	//avec zi+1=-z1 +2(yi+1 - yi)/(xi+1-xi)
		z0 = 1.0;
		z1 = z0; // -1.0 + 2 * borne / borne = -1.0 + 2
		z2 = -z1 + 2.0 * (borne /ratio) / borne;
	}
	attack_elapsed=.0;
	delay_elapsed=.0;
	delay_factor=.0;
	borne=0.0-floor;
}


void FilterCompressor::Tune(DynArray<int> conf){
	//1 attack, 2 delay, 3 soft etc
	switch(conf.size()){
		case 7 :
			bitrate=conf[6];
		case 6 :
			type=conf[5];
		case 5 :
			ratio = conf[4];
		case 4 :
			floor = conf[3]/100.;
		case 3 : 
			soft = conf[2];
		case 2 :
			delay = conf[1]/10.;
		case 1 : 
			attack = conf[0]/10.;
			break;
		default :
			break;
	}
}
//S(x) = yi + zi(x-xi) + ((zi+1 - zi)/2(xi+1 -xi))(x-xi)²
//avec zi+1=-z1 +2(yi+1 - yi)/(xi+1-xi)
double FilterCompressor::get_t(double data){
	//4*((x1 - x0)**2) - 4*(x0 - 2*x1 + x2)*(x0 - x)
	//x1 => floor , x2 => floor+borne, x0 => floor-borne
	double x1 = floor;
	double x2 = floor+borne; 
	double x0 = floor-borne;
	double x = data;
	if( x1 - x0 != x2 -x1){
		double delta =  4. * sqr(x1-x0) - 4. * (x0 - 2. * x1 + x2) * (x0 - x);
		if(delta > .0){
			/*
			t1 = (-2*(x1-x0) - Math.sqrt(delta))/(2*(x0 - 2*x1 + x2))
			t2 = (-2*(x1-x0) + Math.sqrt(delta))/(2*(x0 - 2*x1 + x2))
			*/
			double t1 =  -2.0 * (x1-x0) - sqrt(delta);
			double t2 = -2.0 * (x1-x0) + sqrt(delta);
			if(t1 >= 0.0 && t1 <=1.0){
				return t1;
			}else if( t2 >= 0.0 && t2 <= 2.0){
				return t2;
			}else
				return false;
		}else if(delta == .0){
			return (-2.0*(x1-x0));
		}else{
			return false;
		}
	}else{
		return (x - x0)/(x2 - x0);	
	}

}


double FilterCompressor::grabLevel(double data){
	double ret=data;
	if(soft==2){
		double tmpdata= 20.0 * log10(data);
		tmpdata = get_t(tmpdata);
		ret = bezier_quad(floor-borne, floor, (floor + (borne/(ratio+0.0))), tmpdata);
		ret = pow(10.0, ret/20.0);
	}else if(soft==1){
		double tmpdata= 20.0 * log10(data);
		if(tmpdata>floor){
			ret = floor + z1 * (tmpdata-floor) + (z2 - z1)* (tmpdata-floor)*(tmpdata-floor)/( 2.0 * borne);
		}else if(tmpdata>(floor - borne)){
			ret = (floor-borne) + z0 * (tmpdata -(floor- borne )) + (z1 - z0)* (tmpdata- (floor-borne))*(tmpdata- (floor-borne))/( 2.0 * (floor - borne));
		}
		ret = pow(10.0, ret/20.0);
	}else{
		if(data>rfloor)
		ret = rfloor * pow(data / rfloor, 1.0 / ratio );
	}
	return ret;
}


double FilterCompressor::compress(double data){
	double b=grabLevel(data);
	double cpy = data;
	if(type==NORMAL){
		data = data  - (attack_elapsed / attack) * (data - b);	
	}
	delay_factor=cpy-data;
	return data;
}

double FilterCompressor::delay_effect(double data){
	data = data * (1.0 - delay_elapsed / delay) * delay_factor;
}

Buffer& FilterCompressor::Preview( Buffer &b){
	double smp_duration = 1000.0 / bitrate;//in ms
	cfloor = pow(10.0, (floor-borne)/20.0);
	rfloor = pow(10.0, floor/20.0);
	for(uint i = 0; i< b.size();i++){
		double cpy=b[i];
		double signe = b[i]>0.?1.:-1.;
		if( (signe*b[i])>=cfloor){
			if(attack_elapsed>attack) attack_elapsed=attack;
			b[i]=signe*compress(signe*b[i]);
			attack_elapsed+=smp_duration;
		}else if(attack_elapsed!=0.){
			attack_elapsed=0.;
			b[i]=signe*delay_effect(signe*b[i]);
			delay_elapsed+=smp_duration;
		}
	}
	return b;
}

Buffer& FilterCompressor::Execution(Buffer& b){
	return Preview(b);
}

double * FilterCompressor::Preview(double *data, int size){
	double smp_duration = 1000.0 / bitrate;//in ms
	cfloor = pow(10.0, (floor-borne)/20.0);
	rfloor = pow(10.0, floor/20.0);
	for(uint i = 0; i< size;i++){
		double cpy=data[i];
		double signe = data[i]>0.?1.:-1.;
		if( (signe*data[i])>=cfloor){
			if(attack_elapsed>attack) attack_elapsed=attack;
			data[i]=signe*compress(signe*data[i]);
			attack_elapsed+=smp_duration;
		}else if(attack_elapsed!=0.){
			attack_elapsed=0.;
			data[i]=signe*delay_effect(signe*data[i]);
			delay_elapsed+=smp_duration;
		}
	}
	return data;
}

double *FilterCompressor::Execution(double *data, int size){
	return Preview(data,size);
}

}


